/*
 *  Created by ZhiHui Liu
 */


#include "ngx_http_upconf_module.h"
#include "ngx_http_json.h"


typedef struct ngx_http_upconf_timer_ctx_s ngx_http_upconf_timer_ctx_t;
typedef struct ngx_http_upconf_server_s ngx_http_upconf_server_t;


struct ngx_http_upconf_server_s {
    ngx_uint_t                      weight;
    ngx_uint_t                      max_fails;
    ngx_uint_t                      fail_timeout;

    unsigned                        down:1;
    unsigned                        backup:1;

    u_char                          name[NGX_SOCKADDR_STRLEN];
};


struct ngx_http_upconf_timer_ctx_s {
    ngx_pool_t                      *pool;
    ngx_event_t                     timer;
    ngx_array_t                     servers;
    ngx_http_upstream_rr_peers_t    *peers;

    ngx_queue_t                     queue;
};


typedef struct {
    ngx_uint_t                      index;
    ngx_msec_t                      version;
    ngx_queue_t                     queue;
    ngx_uint_t                      updated;

    ngx_uint_t                      server_n;
    ngx_http_upconf_server_t        servers[8];
} ngx_http_upconf_upstream_t;


typedef struct {
    ngx_uint_t                      upstream_n;
    ngx_queue_t                     *upstreams;
    ngx_shmtx_t                     *mutex;
    ngx_slab_pool_t                 *shpool;

    ngx_uint_t                      worker_n;
    ngx_msec_t                      version;

    ngx_queue_t                     delay_ctxs;
} ngx_http_upconf_ctx_t;


typedef struct {
    ngx_uint_t                      index;
    ngx_uint_t                      generation;  /* not used for now */
    ngx_open_file_t                 *conf_file;
    ngx_http_upconf_upstream_t      *upstream;

    ngx_http_upconf_timer_ctx_t     *octx;
    ngx_http_upconf_timer_ctx_t     *ctx;
} ngx_http_upconf_srv_conf_t;


typedef struct {
    ngx_uint_t                      interval;
    ngx_pool_t                      *pool;
    ngx_event_t                     timer;      /* update from shared memory */
} ngx_http_upconf_main_conf_t;


typedef struct {
    ngx_buf_t                       *err;
    ngx_uint_t                      type;
    ngx_pool_t                      *pool;
    ngx_array_t                     servers;
    ngx_array_t                     *old_servers;
    ngx_array_t                     *new_servers;
    ngx_http_upstream_srv_conf_t    *uscf;
} ngx_http_upconf_tmp_conf_t;



static char *ngx_http_upconf(ngx_conf_t *cf, ngx_command_t *command, void  *conf);
static char *ngx_http_upconf_dump_path(ngx_conf_t *cf, ngx_command_t *command, void *conf);
static char *ngx_http_upconf_interface(ngx_conf_t *cf, ngx_command_t *command, void *conf);

static void *ngx_http_upconf_create_main_conf(ngx_conf_t *cf);
static void *ngx_http_upconf_create_srv_conf(ngx_conf_t *cf);

static ngx_int_t ngx_http_upconf_init_shm_zone(ngx_shm_zone_t *shm_zone, void *data);
static ngx_int_t ngx_http_upconf_init_process(ngx_cycle_t *cycle);


static void ngx_http_upconf_delay_timer_handler(ngx_event_t *ev);
static void ngx_http_upconf_update_handler(ngx_event_t *ev);
static void ngx_http_upconf_delete_delay_timers();


static ngx_int_t ngx_http_upconf_dump_upstream(ngx_http_upstream_srv_conf_t *uscf);

static ngx_int_t ngx_http_upconf_update_upstream(ngx_http_upconf_tmp_conf_t *tcf);
static ngx_int_t ngx_http_upconf_show(ngx_http_request_t *r, ngx_chain_t *out);
static void ngx_http_upconf_send_response(ngx_http_request_t *r, ngx_int_t status, ngx_buf_t *content);
static ngx_buf_t *ngx_http_upconf_read_body_from_file(ngx_http_request_t *r);
static ngx_buf_t *ngx_http_upconf_read_body_from_mem(ngx_http_request_t *r);
static void ngx_http_upconf_body_handler(ngx_http_request_t *r);

static ngx_int_t ngx_http_upconf_interface_handler(ngx_http_request_t *r);



static ngx_http_upconf_ctx_t        *ngx_http_upconf_ctx = NULL;
static ngx_str_t                    ngx_http_upconf_module_shm_name
        = ngx_string("ngx_http_upconf_module");


static ngx_command_t  ngx_http_upconf_commands[] = {

        {
                ngx_string("upconf"),
                NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE1,
                ngx_http_upconf,
                NGX_HTTP_MAIN_CONF_OFFSET,
                0,
                NULL
        },

        {
                ngx_string("upconf_interface"),
                NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS,
                ngx_http_upconf_interface,
                NGX_HTTP_LOC_CONF_OFFSET,
                0,
                NULL
        },
        {
                ngx_string("upconf_dump_path"),
                NGX_HTTP_UPS_CONF | NGX_CONF_TAKE1,
                ngx_http_upconf_dump_path,
                NGX_HTTP_SRV_CONF_OFFSET,
                0,
                NULL
        },

        ngx_null_command
};


static ngx_http_module_t  ngx_http_upconf_module_ctx = {
        NULL,                                  /* preconfiguration */
        NULL,                                  /* postconfiguration */

        ngx_http_upconf_create_main_conf,      /* create main configuration */
        NULL,                                  /* init main configuration */

        ngx_http_upconf_create_srv_conf,       /* create server configuration */
        NULL,                                  /* merge server configuration */

        NULL,                                  /* create location configuration */
        NULL                                   /* merge location configuration */
};


ngx_module_t  ngx_http_upconf_module = {
        NGX_MODULE_V1,
        &ngx_http_upconf_module_ctx,           /* module context */
        ngx_http_upconf_commands,              /* module directives */
        NGX_HTTP_MODULE,                       /* module type */
        NULL,                                  /* init master */
        NULL,                                  /* init module */
        ngx_http_upconf_init_process,          /* init process */
        NULL,                                  /* init thread */
        NULL,                                  /* exit thread */
        NULL,                                  /* exit process */
        NULL,                                  /* exit master */
        NGX_MODULE_V1_PADDING
};


static void *
ngx_http_upconf_create_main_conf(ngx_conf_t *cf)
{
    ngx_http_upconf_main_conf_t             *ucmcf;

    ucmcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_upconf_main_conf_t));
    if(ucmcf == NULL) {
        return NULL;
    }

    ucmcf->interval = 5000;

    return ucmcf;
}


static char *
ngx_http_upconf(ngx_conf_t *cf, ngx_command_t *command, void  *conf)
{
    ngx_int_t                           n;
    ngx_str_t                           *value;
    ngx_shm_zone_t                      *shm_zone;
    ngx_http_upconf_ctx_t               *ctx;
    ngx_http_upconf_main_conf_t         *ucmcf;

    value = cf->args->elts;
    ucmcf = ngx_http_conf_get_module_main_conf(cf, 
                                               ngx_http_upconf_module);

    if(value[1].len <= sizeof("interval=") - 1) {
        goto invalid;
    }

    if(ngx_strncmp(value[1].data, "interval=", 9) != 0) {
        goto invalid;
    }

    n = ngx_atoi(value[1].data + 9, value[1].len - 9);
    if(n < 1000) {
        goto invalid;
    }
    ucmcf->interval = (ngx_uint_t) n;

    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_upconf_ctx_t));
    if(ctx == NULL) {
        return NGX_CONF_ERROR;
    }

    ngx_queue_init(&ctx->delay_ctxs);

    shm_zone = ngx_shared_memory_add(cf,
                                     &ngx_http_upconf_module_shm_name,
                                     4 * 1024 * 1024,
                                     &ngx_http_upconf_module);
    if(shm_zone == NULL) {
        return NGX_CONF_ERROR;
    }

    shm_zone->init = ngx_http_upconf_init_shm_zone;
    shm_zone->data = ctx;

    ngx_http_upconf_ctx = ctx;

    return NGX_CONF_OK;

invalid:
    ngx_conf_log_error(NGX_LOG_ERR, cf, 0,
                        "upconf: invalid params");

    return NGX_CONF_ERROR;
}


static char *
ngx_http_upconf_dump_path(ngx_conf_t *cf, ngx_command_t *command, void *conf)
{
    u_char                              *buf;
    ngx_str_t                           *value, file;
    ngx_http_upconf_srv_conf_t          *ucscf;
    ngx_http_upstream_srv_conf_t        *uscf;

    uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
    ucscf = conf;
    value = cf->args->elts;

    buf = ngx_pcalloc(cf->pool, 1024);
    if(buf == NULL) {
        return NGX_CONF_ERROR;
    }

    file.data = buf;
    ngx_memcpy(buf, value[1].data, value[1].len);
    file.len = value[1].len;

    if(buf[file.len - 1] != '/') {
        file.len += 1;
        buf[file.len - 1] = '/';
    }

    ngx_memcpy(buf + file.len, uscf->host.data, uscf->host.len);
    file.len += uscf->host.len;

    ngx_memcpy(buf + file.len, ".conf", 5);
    file.len += 5;

    ucscf->conf_file = ngx_conf_open_file(cf->cycle, &file);
    if(ucscf->conf_file == NULL) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
                            "open dump file \"%V\"", &file);

        return NGX_CONF_ERROR;
    }

    return NGX_CONF_OK;
}


static char *
ngx_http_upconf_interface(ngx_conf_t *cf, ngx_command_t *command, void *conf)
{
    ngx_http_core_loc_conf_t            *clcf;

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_upconf_interface_handler;

    return NGX_CONF_OK;
}


static ngx_int_t
ngx_http_upconf_init_shm_zone(ngx_shm_zone_t *shm_zone, void *data)
{
    ngx_queue_t                     *q;
    ngx_slab_pool_t                 *shpool;
    ngx_http_upconf_ctx_t           *ctx;

    ctx = ngx_http_upconf_ctx;
    shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;

    q = ngx_slab_alloc(shpool, sizeof(ngx_queue_t));
    if(q == NULL) {
        return NGX_ERROR;
    }

    ngx_queue_init(q);
    ctx->upstreams = q;
    ctx->mutex = &shpool->mutex;
    ctx->shpool = shpool;

    return NGX_OK;
}


static void *ngx_http_upconf_create_srv_conf(ngx_conf_t *cf)
{
    ngx_http_upconf_srv_conf_t      *ucscf;

    ucscf = ngx_pcalloc(cf->pool, sizeof(ngx_http_upconf_srv_conf_t));
    if(ucscf == NULL) {
        return NULL;
    }

    return ucscf;
}


static ngx_int_t
ngx_http_upconf_init_process(ngx_cycle_t *cycle)
{
    ngx_msec_t                          delay;
    ngx_uint_t                          i, j;
    ngx_queue_t                         *q;
    ngx_core_conf_t                     *ccf;
    ngx_http_upconf_ctx_t               *ctx;
    ngx_http_upconf_srv_conf_t          *ucscf;
    ngx_http_upconf_upstream_t          *upstream;
    ngx_http_upstream_server_t          *us;
    ngx_http_upconf_main_conf_t         *ucmcf;
    ngx_http_upstream_srv_conf_t        *uscf, **uscfp;
    ngx_http_upstream_main_conf_t       *umcf;

    ctx = ngx_http_upconf_ctx;

    if(ctx == NULL) {
        /* module is not enable */
        return NGX_OK;
    }

    umcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_upstream_module);
    ucmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_upconf_module);

    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);

    if(umcf->upstreams.nelts == 0) {
        ngx_log_error(NGX_LOG_ERR, cycle->log, 0,
                      "upconf is enabled but no upstream found, "
                      "and upconf will not work");

        return NGX_OK;
    }

    if(ngx_process == NGX_PROCESS_SINGLE) {
        /* master off */
        ctx->worker_n = 1;
    } else {
        ctx->worker_n = (ngx_uint_t) ccf->worker_processes;
    }

    uscfp = umcf->upstreams.elts;

    ngx_shmtx_lock(ctx->mutex);

    if(! ngx_queue_empty(ctx->upstreams)) {
        /* not empty */
        q = ngx_queue_head(ctx->upstreams);
        upstream = ngx_queue_data(q, ngx_http_upconf_upstream_t, queue);

        ctx->version = upstream->version;

        ngx_log_error(NGX_LOG_INFO, cycle->log, 0,
                      "upconf: upstream queue is already sorted");

        for(q = ngx_queue_head(ctx->upstreams);
            q != ngx_queue_sentinel(ctx->upstreams);
            q = ngx_queue_next(q)) {
            upstream = ngx_queue_data(q, ngx_http_upconf_upstream_t, queue);

            uscf = uscfp[upstream->index];
            ucscf = ngx_http_conf_upstream_srv_conf(uscf, ngx_http_upconf_module);
            ucscf->upstream = upstream;
        }

        goto done;
    }

    ctx->version = ngx_current_msec;
    uscfp = umcf->upstreams.elts;
    for(i = 0; i < umcf->upstreams.nelts; ++i) {
        uscf = uscfp[i];
        ucscf = ngx_http_conf_upstream_srv_conf(uscf, ngx_http_upconf_module);

        upstream = ngx_slab_alloc_locked(ctx->shpool,
                                         sizeof(ngx_http_upconf_upstream_t));
        if(upstream == NULL) {
            goto error;
        }

        upstream->updated = ctx->worker_n;
        upstream->version = ngx_current_msec;
        upstream->index = i;
        ngx_queue_init(&upstream->queue);
        ngx_queue_insert_tail(ctx->upstreams, &upstream->queue);
        ucscf->upstream = upstream;

        for(j = 0; j < uscf->servers->nelts; ++j) {
            us = ((ngx_http_upstream_server_t *)uscf->servers->elts) + j;

            upstream->servers[j].weight = us->weight;
            upstream->servers[j].max_fails = us->max_fails;
            upstream->servers[j].fail_timeout = us->fail_timeout;
            upstream->servers[j].backup = us->backup;
            upstream->servers[j].down = us->down;

            ngx_memcpy(upstream->servers[j].name, us->name.data, us->name.len);
        }
        upstream->server_n = uscf->servers->nelts;
    }

done:

    ngx_shmtx_unlock(ctx->mutex);

    ucmcf->timer.data = ucmcf;
    ucmcf->timer.handler = ngx_http_upconf_update_handler;
    ucmcf->timer.log = cycle->log;

    srand(ngx_pid);
    delay = ngx_random() % ucmcf->interval;

    ngx_event_add_timer(&ucmcf->timer, delay);

    return NGX_OK;

error:
    ngx_shmtx_unlock(ctx->mutex);
    return NGX_ERROR;
}


static void
ngx_http_upconf_delay_timer_handler(ngx_event_t *ev)
{
    ngx_http_upconf_timer_ctx_t         *ctx;

    ctx = ev->data;

    ngx_queue_remove(&ctx->queue);

    ngx_log_error(NGX_LOG_INFO, ev->log, 0,
                  "delete pool %p", ctx->pool);

    ngx_destroy_pool(ctx->pool);

}


static void
ngx_http_upconf_delete_delay_timers()
{
    ngx_queue_t                         *q;
    ngx_http_upconf_timer_ctx_t         *timer_ctx;

    for(q = ngx_queue_head(&ngx_http_upconf_ctx->delay_ctxs);
            q != ngx_queue_sentinel(&ngx_http_upconf_ctx->delay_ctxs);
            q = ngx_queue_next(q))
    {
        timer_ctx = ngx_queue_data(q, ngx_http_upconf_timer_ctx_t, queue);

        if(timer_ctx->timer.timer_set) {
            ngx_event_del_timer(&timer_ctx->timer);

            ngx_destroy_pool(timer_ctx->pool);
        }
    }
}


static void
ngx_http_upconf_update_handler(ngx_event_t *ev)
{
    ngx_queue_t                         *q;
    ngx_http_upconf_ctx_t               *ctx;
    ngx_http_upconf_server_t            servers[8];
    ngx_http_upconf_upstream_t          *upstream;
    ngx_http_upconf_tmp_conf_t          tcf;
    ngx_http_upconf_main_conf_t         *ucmcf;
    ngx_http_upstream_main_conf_t       *umcf;
    ngx_http_upstream_srv_conf_t        **uscfp;

    if(ngx_quit || ngx_exiting || ngx_terminate) {
        ngx_http_upconf_delete_delay_timers();
        return;
    }

    ngx_memzero(servers, 8 * sizeof(ngx_http_upconf_server_t));

    tcf.servers.elts = servers;
    tcf.servers.nalloc = 8;
    tcf.servers.nelts = 0;
    tcf.servers.size = sizeof(ngx_http_upconf_server_t);

    umcf = ngx_http_cycle_get_module_main_conf(ngx_cycle,
                                               ngx_http_upstream_module);

    ucmcf = ev->data;
    ctx = ngx_http_upconf_ctx;

    ngx_shmtx_lock(ctx->mutex);

    q = ngx_queue_head(ctx->upstreams);
    upstream = ngx_queue_data(q, ngx_http_upconf_upstream_t, queue);

    if(upstream->version == ctx->version) {
        ngx_shmtx_unlock(ctx->mutex);

        goto done;
    }

    uscfp = umcf->upstreams.elts;

    for(q = ngx_queue_head(ctx->upstreams);
        q != ngx_queue_sentinel(ctx->upstreams);
        q = ngx_queue_next(q))
    {
        upstream = ngx_queue_data(q, ngx_http_upconf_upstream_t, queue);

        if(ctx->version == upstream->version) {
            break;
        }

        /* update */

        tcf.uscf = uscfp[upstream->index];

        ngx_memcpy(tcf.servers.elts, upstream->servers,
                   upstream->server_n * sizeof(ngx_http_upconf_server_t));
        ngx_http_upconf_update_upstream(&tcf);

        upstream->updated += 1;
    }

    q = ngx_queue_head(ctx->upstreams);
    upstream = ngx_queue_data(q, ngx_http_upconf_upstream_t, queue);

    ctx->version = upstream->version;

    ngx_shmtx_unlock(ctx->mutex);

    ngx_log_error(NGX_LOG_INFO, ev->log, 0,
                  "upconf: update success");

done:

    ngx_event_add_timer(ev, ucmcf->interval);
}


static u_char *
ngx_sprintf_escape(u_char *dst, ngx_str_t *str)
{
    u_char                          *src, ch;
    ngx_uint_t                      s, d;

    s = 0;
    d = 0;
    src = str->data;

    while(s < str->len) {
        ch = src[s];

        switch (ch) {
            case '\r':
                dst[d] = '\\';
                d += 1;
                dst[d] = 'r';
                break;
            case '\t':
                dst[d] = '\\';
                d += 1;
                dst[d] = 't';
                break;
            case '\n':
                dst[d] = '\\';
                d += 1;
                dst[d] = 'n';
                break;
            default:
                dst[d] = src[s];
                break;
        }

        s++;
        d++;
    }

    return src;
}


static ngx_int_t
ngx_http_upconf_dump_upstream(ngx_http_upstream_srv_conf_t *uscf)
{
    u_char                          *last_slash;
    ngx_buf_t                       *b;
    ngx_str_t                       path;
    ngx_uint_t                      i;
    ngx_pool_t                      *pool;
    ngx_http_upstream_server_t      *server;
    ngx_http_upconf_srv_conf_t      *ucscf;

    ucscf = ngx_http_conf_upstream_srv_conf(uscf, ngx_http_upconf_module);

    pool = ngx_create_pool(4096 * 4, ngx_cycle->log);
    if(pool == NULL) {
        return NGX_ERROR;
    }

    b = ngx_create_temp_buf(pool, 4096);
    if(b == NULL) {
        return NGX_ERROR;
    }

    b->last = ngx_sprintf(b->last, "upstream %V {\n", &uscf->host);

    for(i = 0; i < uscf->servers->nelts; ++i) {
        server = ((ngx_http_upstream_server_t *) uscf->servers->elts) + i;

        b->last = ngx_sprintf(b->last,
                              "    server              %V "
                              "weight=%ul max_fails=%ul "
                              "fail_timeout=%uls%s;\n",
                              &server->name,
                              server->weight,
                              server->max_fails,
                              server->fail_timeout,
                              (server->backup == 1) ? "backup" : "");
    }


#if (NGX_HTTP_UPSTREAM_KEEPALIVE)
    ngx_http_upstream_keepalive_srv_conf_t  *ukscf;

    ukscf = ngx_http_conf_upstream_srv_conf(uscf,
                    ngx_http_upstream_keepalive_module);
    if(ukscf != NULL) {
        b->last = ngx_sprintf(b->last,
                              "\n    keepalive"
                              "           %ul;\n",
                              ukscf->max_cached);
    }

#endif

#if (NGX_HTTP_UPSTREAM_CHECK)
    ngx_http_upstream_check_srv_conf_t  *ckscf;
    u_char                              *escaped_send = NULL;

    ckscf = ngx_http_conf_upstream_srv_conf(uscf,
                    ngx_http_upstream_check_module);
    escaped_send = ngx_pcalloc(pool, ckscf->send.len * 2);
    if(escaped_send == NULL) {
        goto error;
    }

    ngx_sprintf_escape(escaped_send, &ckscf->send);

    if(ckscf == NULL || ckscf->check_type_conf == NULL) {
        goto next;
    }

    b->last = ngx_sprintf(b->last,
                          "\n    check"
                          "               interval=%d rise=%d "
                          "fall=%d timeout=%d type=%V default_down=%s;\n",
                          ckscf->check_interval,
                          ckscf->rise_count,
                          ckscf->fall_count,
                          ckscf->check_timeout,
                          &ckscf->check_type_conf->name,
                          ckscf->default_down ? "true" : "faile");

    b->last = ngx_sprintf(b->last,
                          "    check_http_send"
                          "             \"%s\";\n",
                          escaped_send);

    b->last = ngx_sprintf(b->last,
                          "    check_http_expect_alive     http_2xx;\n");

    b->last = ngx_sprintf(b->last,
                          "    check_keepalive_requests    %d;\n",
                          ckscf->check_keepalive_requests);

next:

#endif

    /* dump upconf config*/
    if(ucscf->conf_file != NULL) {
        path = ucscf->conf_file->name;
        last_slash = path.data + path.len;

        while(last_slash > path.data) {
            if(*last_slash == '/') {
                break;
            }

            last_slash--;
        }

        path.len = last_slash - path.data;

        b->last = ngx_sprintf(b->last,
                              "\n    upconf_dump_path    %V;\n",
                              &path);
    }

    b->last = ngx_sprintf(b->last, "}\n");

    ucscf->conf_file->fd = ngx_open_file(ucscf->conf_file->name.data,
                                         NGX_FILE_TRUNCATE,
                                         NGX_FILE_WRONLY,
                                         NGX_FILE_DEFAULT_ACCESS);
    if(ucscf->conf_file->fd == NGX_INVALID_FILE) {
        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,
                      "open conf file error \"%V\"", &ucscf->conf_file->name);

        return NGX_ERROR;
    }

    ngx_lseek(ucscf->conf_file->fd, 0, SEEK_SET);
    if(ngx_write_fd(ucscf->conf_file->fd, b->pos, b->last  - b->pos) == NGX_ERROR) {
        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,
                      "write conf file error \"%V\"", &ucscf->conf_file->name);

        ngx_close_file(ucscf->conf_file->fd);
        return NGX_ERROR;
    }

    if(ngx_ftruncate(ucscf->conf_file->fd, b->last - b->pos) != NGX_OK) {
        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,
                      "truncate conf file error \"%V\"", &ucscf->conf_file->name);

        ngx_close_file(ucscf->conf_file->fd);
        return NGX_ERROR;
    }

    ngx_close_file(ucscf->conf_file->fd);
    ucscf->conf_file->fd = NGX_INVALID_FILE;

    ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0,
                  "upconf dump conf \"%V\" success", &ucscf->conf_file->name);

    ngx_destroy_pool(pool);

    return NGX_OK;

error:
    ngx_destroy_pool(pool);
    return NGX_ERROR;
}


static ngx_int_t
ngx_http_upconf_show(ngx_http_request_t *r, ngx_chain_t *out)
{
    ngx_buf_t                           *buf;
    ngx_uint_t                          i, j;
    ngx_queue_t                         *q;
    ngx_http_upconf_ctx_t               *ctx;
    ngx_http_upconf_server_t            *shm_server;
    ngx_http_upconf_srv_conf_t          *ucscf;
    ngx_http_upconf_upstream_t          *upstream;
    ngx_http_upstream_srv_conf_t        *uscf, **uscfp;
    ngx_http_upstream_main_conf_t       *umcf;

    ctx = ngx_http_upconf_ctx;
    umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);

    buf = ngx_create_temp_buf(r->pool, 4096);
    if(buf == NULL) {
        return NGX_ERROR;
    }

    out->buf = buf;

    ngx_shmtx_lock(ctx->mutex);

    q = ngx_queue_head(ctx->upstreams);
    upstream = ngx_queue_data(q, ngx_http_upconf_upstream_t, queue);
    if(upstream->updated != ctx->worker_n) {
        buf->last = ngx_sprintf(buf->last,
                                "%d workers are updating...\n",
                                ctx->worker_n - upstream->updated);
    }

    uscfp = umcf->upstreams.elts;
    for(i = 0; i < umcf->upstreams.nelts; ++i) {
        uscf = uscfp[i];
        ucscf = ngx_http_conf_upstream_srv_conf(uscf,
                                                ngx_http_upconf_module);
        upstream = ucscf->upstream;

        buf->last = ngx_sprintf(buf->last, "upstream %V\n", &uscf->host);

        for(j = 0; j < upstream->server_n; ++j) {
            shm_server = upstream->servers + j;

            buf->last = ngx_sprintf(buf->last,
                                    "        server %s weight=%ul max_fails=%ul "
                                    "fail_timeout=%uls%s%s\n",
                                    shm_server->name,
                                    shm_server->weight,
                                    shm_server->max_fails,
                                    shm_server->fail_timeout,
                                    (shm_server->down == 1) ? " down" : "",
                                    (shm_server->backup == 1) ? " backup" : "");
        }
    }

    ngx_shmtx_unlock(ctx->mutex);

    ngx_str_set(&r->headers_out.content_type, "text/plain");
    r->headers_out.content_length_n = buf->last - buf->pos;

    out->buf = buf;
    buf->last_buf = 1;
    out->next = NULL;

    return NGX_OK;
}


static ngx_int_t
ngx_http_upconf_update_upstream(ngx_http_upconf_tmp_conf_t *tcf)
{
    u_char                          *port, *last;
    ngx_int_t                       n;
    ngx_addr_t                      *addrs;
    ngx_uint_t                      i, w;
    ngx_pool_t                      *pool;
    ngx_event_t                     *timer;
    struct sockaddr_in              *sin;
    ngx_http_upconf_server_t        *tmp_server;
    ngx_http_upconf_srv_conf_t      *ucscf;
    ngx_http_upstream_server_t      *us;
    ngx_http_upstream_rr_peer_t     *peer;
    ngx_http_upconf_timer_ctx_t     *timer_ctx, *octx;
    ngx_http_upstream_srv_conf_t    *uscf;
    ngx_http_upstream_rr_peers_t    *peers, *opeers;

    uscf = tcf->uscf;
    ucscf = ngx_http_conf_upstream_srv_conf(uscf, ngx_http_upconf_module);

    pool = ngx_create_pool(2048, ngx_cycle->log);
    if(pool == NULL) {
        goto error;
    }

    timer_ctx = ngx_pcalloc(pool, sizeof(ngx_http_upconf_timer_ctx_t));
    if(timer_ctx == NULL) {
        goto error;
    }
    timer_ctx->pool = pool;
    ngx_log_error(NGX_LOG_DEBUG, ngx_cycle->log, 0,
                  "create pool %p at %d", pool, __LINE__);


    if(ngx_array_init(&timer_ctx->servers, pool, 8,
                      sizeof(ngx_http_upstream_server_t)) != NGX_OK) {
        goto error;
    }

    for(i = 0; i < tcf->servers.nelts; ++i) {
        tmp_server = ((ngx_http_upconf_server_t *)tcf->servers.elts) + i;

        us = ngx_array_push(&timer_ctx->servers);
        if(us == NULL) {
            goto error;
        }

        us->name.data = ngx_pcalloc(pool, NGX_SOCKADDR_STRLEN);
        if(us->name.data == NULL) {
            goto error;
        }

        us->name.len = ngx_strlen(tmp_server->name);
        ngx_memcpy(us->name.data, tmp_server->name, us->name.len);

        addrs = ngx_pcalloc(pool, sizeof(ngx_addr_t));
        if(addrs == NULL) {
            goto error;
        }

        sin = ngx_pcalloc(pool, sizeof(struct sockaddr_in));
        if(sin == NULL) {
            goto error;
        }

        last = (u_char *)tmp_server->name + ngx_strlen(tmp_server->name);
        port = ngx_strlchr(tmp_server->name, last, ':');
        n = ngx_atoi(port + 1, last - port - 1);
        if(n < 1 || n > 65535) {
            ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
                          "upconf: invalid port number \"%l\"", n);
            goto error;
        }

        sin->sin_family = AF_INET;
        sin->sin_port = htons((in_port_t) n);
        sin->sin_addr.s_addr = ngx_inet_addr(tmp_server->name,
                                             port - tmp_server->name);

        if(sin->sin_addr.s_addr == INADDR_NONE) {
            ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
                          "upconf: invalid ip in %s\n", tmp_server->name);
            goto error;
        }

        addrs->sockaddr = (struct sockaddr *) sin;
        addrs->name = us->name;
        addrs->socklen = sizeof(struct sockaddr_in);

        us->host.data = us->name.data;
        us->host.len = port - tmp_server->name;
        us->addrs = addrs;
        us->naddrs = 1;
        us->down = tmp_server->down;
        us->backup = tmp_server->backup;
        us->weight = tmp_server->weight;
        us->max_fails = tmp_server->max_fails;
        us->fail_timeout = tmp_server->fail_timeout;
    }

    opeers = uscf->peer.data;
    peers = ngx_pcalloc(pool, sizeof(ngx_http_upstream_rr_peers_t)
                              + tcf->servers.nelts
                              * sizeof(ngx_http_upstream_rr_peer_t));
    if(peers == NULL) {
        goto error;
    }

    w = 0;
    for(i = 0; i < timer_ctx->servers.nelts; ++i) {
        us = ((ngx_http_upstream_server_t *)timer_ctx->servers.elts) + i;
        peer = peers->peer + i;

        peer->name = us->addrs->name;
        peer->host = us->host;
        peer->sockaddr = us->addrs->sockaddr;
        peer->socklen = us->addrs->socklen;
        peer->max_fails = us->max_fails;
        peer->fail_timeout = us->fail_timeout;
        peer->down = us->down;
        peer->weight = us->weight;
        peer->effective_weight = us->weight;
        peer->current_weight = 0;

        w += us->weight;
    }

    peers->single = (tcf->servers.nelts == 1) ? 1 : 0;
    peers->number = tcf->servers.nelts;
    peers->init_number = opeers->init_number;
    peers->weighted = (w != tcf->servers.nelts) ? 1 : 0;
    peers->total_weight = w;

    uscf->peer.data = peers;
    uscf->servers = &timer_ctx->servers;
    peers->next = NULL;

    octx = ucscf->ctx;

    if(octx == NULL) {
        ucscf->ctx = timer_ctx;
        return NGX_OK;
    }

    timer = &octx->timer;
    timer->data = octx;
    timer->handler = ngx_http_upconf_delay_timer_handler;
    timer->log = ngx_cycle->log;

    ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0,
                  "add timer to destory pool %p", octx->pool);

    ngx_queue_init(&octx->queue);
    ngx_queue_insert_head(&ngx_http_upconf_ctx->delay_ctxs,
                          &octx->queue);

    ngx_event_add_timer(timer, NGX_HTTP_UPCONF_DELAY_DELTE);

    ucscf->ctx = timer_ctx;

    ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0,
                  "update worker upstream success");

    return NGX_OK;

error:
    ngx_destroy_pool(pool);
    return NGX_ERROR;
}


static ngx_buf_t *
ngx_http_upconf_read_body_from_file(ngx_http_request_t *r)
{
    size_t                              len;
    ssize_t                             size;
    ngx_buf_t                           *body, *buf;
    ngx_chain_t                         *cl;

    len = 0;

    for(cl = r->request_body->bufs; cl; cl = cl->next) {
        buf = cl->buf;

        if(buf->in_file) {
            len += buf->file_last - buf->file_pos;
        } else {
            len += buf->last - buf->pos;
        }
    }

    body = ngx_create_temp_buf(r->pool, len);
    if(body == NULL) {
        return NULL;
    }

    for(cl = r->request_body->bufs; cl; cl = cl->next) {
        buf = cl->buf;

        if(buf->in_file) {
            size = ngx_read_file(buf->file, body->last,
                                 buf->file_last - buf->file_pos,
                                 buf->file_pos);

            if(size == NGX_ERROR) {
                return NULL;
            }

            body->last += size;
        } else {
            body->last = ngx_cpymem(body->last,
                                    buf->pos,
                                    buf->last - buf->pos);
        }
    }

    return body;
}


static ngx_buf_t *
ngx_http_upconf_read_body_from_mem(ngx_http_request_t *r)
{
    size_t                              len;
    ngx_buf_t                           *buf, *body, *next;
    ngx_chain_t                         *cl;

    cl = r->request_body->bufs;
    buf = cl->buf;

    if(cl->next == NULL) {
        return buf;
    }

    next = cl->next->buf;
    len = (buf->last - buf->pos) + (next->last - next->pos);

    body = ngx_create_temp_buf(r->pool, len);
    if(body == NULL) {
        return NULL;
    }

    body->last = ngx_cpymem(body->last, buf->pos, buf->last - buf->pos);
    body->last = ngx_cpymem(body->last, next->pos, next->last - next->pos);

    return body;
}


static void
ngx_http_upconf_send_response(ngx_http_request_t *r, ngx_int_t status, ngx_buf_t *content)
{
    ngx_int_t                           rv;
    ngx_buf_t                           *b;
    ngx_chain_t                         out;

    r->headers_out.status = status;
    r->headers_out.content_length_n = content->last - content->pos;

    rv = ngx_http_send_header(r);
    if(rv == NGX_ERROR || rv > NGX_OK) {
        ngx_http_finalize_request(r, rv);
        return;;
    }

    if(content->last == content->pos) {
        ngx_http_finalize_request(r, ngx_http_send_special(r, NGX_HTTP_FLUSH));
        return;
    }

    out.buf = content;
    out.next = NULL;
    content->last_buf = 1;

    ngx_http_finalize_request(r, ngx_http_output_filter(r, &out));
}


#if (NGX_HTTP_UPSTREAM_CHECK)
static ngx_int_t
ngx_http_upconf_update_upstream_check(ngx_http_upconf_tmp_conf_t *tcf)
{
    ngx_int_t                           index;
    ngx_uint_t                          i, j, add_n, del_n;
    ngx_http_upstream_server_t          *del_srv[8], *add_srv[8];
    ngx_http_upstream_server_t          *osrv, *nsrv;
    ngx_http_upstream_rr_peers_t        *peers;
    ngx_http_upstream_rr_peer_t         *peer;

    add_n = 0;
    del_n = 0;

    for(i = 0; i < tcf->old_servers->nelts; ++i) {
        osrv = ((ngx_http_upstream_server_t *)tcf->old_servers->elts) + i;

        for(j = 0; j < tcf->new_servers->nelts; ++j) {
            nsrv = ((ngx_http_upstream_server_t *) tcf->new_servers->elts) + j;

            if(osrv->addrs->name.len != nsrv->addrs->name.len) {
                continue;
            }

            if(ngx_strncmp(osrv->addrs->name.data, nsrv->addrs->name.data,
                           osrv->addrs->name.len) == 0) {

                goto del_found;
            }
        }

        /* del */
        del_srv[del_n] = osrv;
        del_n++;

    del_found:
        index = 0;      /* dummy */
    }

    for(i = 0; i < tcf->new_servers->nelts; ++i) {
        nsrv = ((ngx_http_upstream_server_t *) tcf->new_servers->elts) + i;

        for(j = 0; j < tcf->old_servers->nelts; ++j) {
            osrv = ((ngx_http_upstream_server_t *) tcf->old_servers->elts) + j;

            if(nsrv->addrs->name.len != osrv->addrs->name.len) {
                continue;
            }

            if(ngx_strncmp(nsrv->addrs->name.data, osrv->addrs->name.data,
                           osrv->addrs->name.len) == 0) {

                goto add_found;
            }
        }

        /* add */
        add_srv[add_n] = nsrv;
        add_n++;

    add_found:
        index = 0;      /* dummy */
    }


    peers = tcf->uscf->peer.data;
    for(i = 0; i < add_n; ++i) {
        index = ngx_http_upstream_check_add_dynamic_peer(ngx_cycle->pool,
                                                         tcf->uscf,
                                                         add_srv[i]->addrs);

        for(j = 0; j < peers->number; ++j) {
            peer = peers->peer + j;

            if(peer->name.len != add_srv[i]->addrs->name.len) {
                continue;
            }

            if(ngx_strncmp(peer->name.data,
                           add_srv[i]->addrs->name.data,
                           peer->name.len) == 0) {

                peer->check_index = (ngx_uint_t) index;
                goto add_next;
            }
        }

    add_next:
        index = 0;      /* dummy */
    }

    for(i = 0; i < del_n; ++i) {
        ngx_http_upstream_check_delete_dynamic_peer(&tcf->uscf->host,
                                                    del_srv[i]->addrs);
    }

    peers->total_weight = 0;

    for(i = 0; i < peers->number; ++i) {
        peer = peers->peer + i;

        peers->total_weight += peer->weight;
    }

    return NGX_OK;
}
#endif


static void
ngx_http_upconf_body_handler(ngx_http_request_t *r)
{
    cJSON                               *root, *server;
    cJSON                               *servers, *elt, *ups;
    ngx_buf_t                           *body, *content;
    ngx_uint_t                          i;
    ngx_queue_t                         *q;
    ngx_http_upconf_server_t            *tmp_server;
    ngx_http_upconf_tmp_conf_t          *tcf;
    ngx_http_upconf_srv_conf_t          *ucscf;
    ngx_http_upstream_srv_conf_t        *uscf = NULL, **uscfp;
    ngx_http_upstream_main_conf_t       *umcf;

    if(r->request_body->temp_file) {
        body = ngx_http_upconf_read_body_from_file(r);
    } else {
        body = ngx_http_upconf_read_body_from_mem(r);
    }

    if(body == NULL) {
        content = NULL;
        goto finish;
    }

    content = ngx_create_temp_buf(r->pool, 512);
    if(content == NULL) {
        ngx_http_upconf_send_response(r,
                                      NGX_HTTP_INTERNAL_SERVER_ERROR,
                                      NULL);
        return;
    }

    root = cJSON_Parse((const char *)body->pos);
    if(root == NULL) {
        content->last = ngx_sprintf(content->last,
                                    "parse json error: \"%s\"",
                                    cJSON_GetErrorPtr());
        goto invalid;
    }

    ups = cJSON_GetObjectItem(root, "upstream");
    if(ups == NULL) {
        content->last = ngx_sprintf(content->last,
                                    "no upstream found");
        goto invalid;
    }

    servers = cJSON_GetObjectItem(root, "servers");
    if(servers == NULL) {
        content->last = ngx_sprintf(content->last,
                                    "no servers found");
        goto invalid;
    }

    tcf = ngx_pcalloc(r->pool, sizeof(ngx_http_upconf_tmp_conf_t));
    if(tcf == NULL) {
        ngx_http_upconf_send_response(r,
                                      NGX_HTTP_INTERNAL_SERVER_ERROR,
                                      NULL);
        return;
    }

    if(ngx_array_init(&tcf->servers, r->pool, 8,
                      sizeof(ngx_http_upconf_server_t)) != NGX_OK)
    {
        ngx_http_upconf_send_response(r,
                                      NGX_HTTP_INTERNAL_SERVER_ERROR,
                                      NULL);
        return;
    }

    if(cJSON_GetArraySize(servers) == 0) {
        content->last = ngx_sprintf(content->last, "no server in upstreams\n");
        goto invalid;
    }

    for(server = servers->child; server; server = server->next) {
        tmp_server = ngx_array_push(&tcf->servers);
        if(tmp_server == NULL) {
            goto finish;
        }

        tmp_server->weight = 1;
        tmp_server->max_fails = 1;
        tmp_server->fail_timeout = 10;
        tmp_server->backup = 0;
        tmp_server->down = 0;

        elt = cJSON_GetObjectItem(server, "name");
        if(elt == NULL) {
            content->last = ngx_sprintf(content->last,
                                        "server name is not found");
            goto invalid;
        }
        ngx_memcpy(tmp_server->name, elt->valuestring,
                   ngx_strlen(elt->valuestring));

        elt = cJSON_GetObjectItem(server, "weight");
        if(elt != NULL) {
            tmp_server->weight = elt->valueint;

            if(tmp_server->weight < 1) {
                goto invalid;
            }
        }

        elt = cJSON_GetObjectItem(server, "max_fails");
        if(elt != NULL) {
            tmp_server->max_fails = elt->valueint;

            if(tmp_server->max_fails < 1) {
                content->last = ngx_sprintf(content->last,
                                            "max_fails is invalid %d",
                                            tmp_server->max_fails);
                goto invalid;
            }
        }

        elt = cJSON_GetObjectItem(server, "fail_timeout");
        if(elt != NULL) {
            tmp_server->fail_timeout = elt->valueint;

            if(tmp_server->fail_timeout < 1) {
                content->last = ngx_sprintf(content->last,
                                            "fail_timeout is invalid %d\n",
                                            tmp_server->fail_timeout);
            }
        }

        elt = cJSON_GetObjectItem(server, "backup");
        if(elt != NULL) {

            if(elt->type == cJSON_True) {
                tmp_server->backup = 1;
            } else if(elt->type == cJSON_False) {
                tmp_server->backup = 0;
            } else {
                content->last = ngx_sprintf(content->last,
                                            "invalid value type for \"backup\"\n");

                goto invalid;
            }
        }


        elt = cJSON_GetObjectItem(server, "down");
        if(elt != NULL) {

            if(elt->type == cJSON_True) {
                tmp_server->down = 1;
            } else if(elt->type == cJSON_False) {
                tmp_server->down = 0;
            } else {
                content->last = ngx_sprintf(content->last,
                                            "invalid value type for \"down\"\n");

                goto invalid;
            }
        }
    }

    umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);
    uscfp = umcf->upstreams.elts;
    for(i = 0; i < umcf->upstreams.nelts; ++i) {
        uscf = uscfp[i];

        if(uscf->host.len != ngx_strlen(ups->valuestring)) {
            continue;
        }

        if(ngx_strncmp(uscf->host.data, ups->valuestring, uscf->host.len) == 0) {
            tcf->uscf = uscf;
            tcf->old_servers = uscf->servers;
            goto found;
        }
    }

    /* not found */
    content->last = ngx_sprintf(content->last,
                                "no upstream named %s\n",
                                ups->valuestring);
    ngx_http_upconf_send_response(r, NGX_HTTP_NOT_FOUND, content);

found:

    ngx_shmtx_lock(ngx_http_upconf_ctx->mutex);

    ngx_http_upconf_update_upstream(tcf);

    ucscf = ngx_http_conf_upstream_srv_conf(uscf, ngx_http_upconf_module);
    q = &ucscf->upstream->queue;

    ngx_queue_remove(q);

    ngx_http_upconf_ctx->version = ngx_current_msec;
    ucscf->upstream->version = ngx_current_msec;
    ucscf->upstream->updated = 1;   /* it's self */
    ucscf->upstream->server_n = tcf->servers.nelts;

    ngx_memcpy(ucscf->upstream->servers, tcf->servers.elts,
               tcf->servers.nelts * sizeof(ngx_http_upconf_server_t));

    ngx_queue_insert_head(ngx_http_upconf_ctx->upstreams, q);

    ngx_shmtx_unlock(ngx_http_upconf_ctx->mutex);

#if (NGX_HTTP_UPSTREAM_CHECK)
    tcf->new_servers = uscf->servers;
    ngx_http_upconf_update_upstream_check(tcf);

#endif

    if(ucscf->conf_file) {
        ngx_http_upconf_dump_upstream(uscf);
    }

    content->last = ngx_sprintf(content->last, "success\n");

finish:
    ngx_http_upconf_send_response(r, 0, content);
    return;

invalid:
    ngx_http_upconf_send_response(r, 0, content);
    return;
}


static ngx_int_t
ngx_http_upconf_interface_handler(ngx_http_request_t *r)
{
    ngx_int_t                           rv;
    ngx_chain_t                         out;


    if(r->method == NGX_HTTP_PUT) {
        /* update */
        rv = ngx_http_read_client_request_body(r, ngx_http_upconf_body_handler);
        if(rv >= NGX_HTTP_SPECIAL_RESPONSE) {
            return rv;
        }

        return NGX_DONE;
    } else if(r->method == NGX_HTTP_GET) {
        rv = ngx_http_upconf_show(r, &out);
    } else {
        return NGX_HTTP_NOT_ALLOWED;
    }

    if(rv == NGX_ERROR) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    rv = ngx_http_send_header(r);
    if(rv != NGX_OK) {
        return rv;
    }

    return ngx_http_output_filter(r, &out);
}


